#include "EditorTokenGenerator.h"
#include "EditorScriptToken.h"
#include <string.h>

SEditorScriptTokenGenerator::SEditorScriptTokenGenerator(const CString &InScriptContent)
:ScriptContent(InScriptContent)
{
}

SEditorTokenGenerateResult SEditorScriptTokenGenerator::GenerateTokens()
{
    while(true)
    {
        if(!GotoNextChar())
        {
            break;
        }

        GenerateOneToken();
    }

    return Result;
}

bool SEditorScriptTokenGenerator::GotoNextChar()
{
    if((CurrentCharIndex+1)<ScriptContent.GetLength())
    {
        CurrentCharIndex++;
        return true;
    }
    return false;
}

void SEditorScriptTokenGenerator::Skip(int64_t Count)
{
    CurrentCharIndex+=Count;
}

char32_t SEditorScriptTokenGenerator::GetCurrentChar() const
{
    return ScriptContent.GetCharAt(CurrentCharIndex);
}

TOptional<char32_t> SEditorScriptTokenGenerator::GetNextChar() const
{
    if((CurrentCharIndex+1)<ScriptContent.GetLength())
    {
        return ScriptContent.GetCharAt(CurrentCharIndex+1);
    }
    return TOptional<char32_t>();
}

void SEditorScriptTokenGenerator::GenerateOneToken()
{        
    char32_t CurrentChar=GetCurrentChar();

    auto IsMatch=[this](const CString& InString) -> bool
    {
        if(CurrentCharIndex+InString.GetLength()>=ScriptContent.GetLength())
        {
            return false;
        }

        if(memcmp(ScriptContent.GetData()+CurrentCharIndex,InString.GetData(),InString.GetLength()*sizeof(char32_t))==0)
        {
            return true;
        }

        return false;
    }; 

    if(IsMatch(U"using"))
    {
        SEditorScriptToken Token;
        Token.Range.StartCharIndex=CurrentCharIndex;
        Token.Range.EndCharIndex=CurrentCharIndex+4;
        Token.TokenType=EEditorScriptTokenType::Using;
        Result.TokenList.Add(Token);

        Skip(4);
        return;
    }

    if(IsMatch(U"namespace"))
    {
        SEditorScriptToken Token;
        Token.Range.StartCharIndex=CurrentCharIndex;
        Token.Range.EndCharIndex=CurrentCharIndex+8;
        Token.TokenType=EEditorScriptTokenType::Namespace;
        Result.TokenList.Add(Token);

        Skip(8);
        return;
    }

    if(IsMatch(U"true"))
    {
        SEditorScriptToken Token;
        Token.Range.StartCharIndex=CurrentCharIndex;
        Token.Range.EndCharIndex=CurrentCharIndex+3;
        Token.TokenType=EEditorScriptTokenType::LiteralBool;
        Result.TokenList.Add(Token);

        Skip(3);
        return;
    }

    if(IsMatch(U"false"))
    {
        SEditorScriptToken Token;
        Token.Range.StartCharIndex=CurrentCharIndex;
        Token.Range.EndCharIndex=CurrentCharIndex+4;
        Token.TokenType=EEditorScriptTokenType::LiteralBool;
        Result.TokenList.Add(Token);

        Skip(4);
        return;
    }

    if(IsMatch(U"class"))
    {
        SEditorScriptToken Token;
        Token.Range.StartCharIndex=CurrentCharIndex;
        Token.Range.EndCharIndex=CurrentCharIndex+5;
        Token.TokenType=EEditorScriptTokenType::Class;
        Result.TokenList.Add(Token);

        Skip(4);
        return;
    }

    if(IsMatch(U"struct"))
    {
        SEditorScriptToken Token;
        Token.Range.StartCharIndex=CurrentCharIndex;
        Token.Range.EndCharIndex=CurrentCharIndex+6;
        Token.TokenType=EEditorScriptTokenType::Struct;
        Result.TokenList.Add(Token);

        Skip(5);
        return;
    }

    if(IsMatch(U"enum"))
    {
        SEditorScriptToken Token;
        Token.Range.StartCharIndex=CurrentCharIndex;
        Token.Range.EndCharIndex=CurrentCharIndex+4;
        Token.TokenType=EEditorScriptTokenType::Enum;
        Result.TokenList.Add(Token);

        Skip(3);
        return;
    }

    static TVector<CString> InnerTypes=
    {
        U"int",
        U"float",
        U"bool",
        U"void",
        U"string",
    };

    for(auto& InnerType : InnerTypes)
    {
        if(IsMatch(InnerType))
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.Range.EndCharIndex=CurrentCharIndex+InnerType.GetLength()-1;
            Token.TokenType=EEditorScriptTokenType::InnerType;
            Result.TokenList.Add(Token);

            Skip(InnerType.GetLength()-1);
            return;
        }
    }


    static TVector<CString> NormalKeywords=
    {
        U"class",
        U"struct",
        U"interface",
        U"namespace",
        U"using",
        U"enum",
        U"const",
        U"static",
        U"public",
        U"protected",
        U"private",
        U"virtual",
        U"override",
        U"new",
    };

    for(auto& Keyword : NormalKeywords)
    {
        if(IsMatch(Keyword))
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.Range.EndCharIndex=CurrentCharIndex+Keyword.GetLength()-1;
            Token.TokenType=EEditorScriptTokenType::NormalKeyword;
            Result.TokenList.Add(Token);

            Skip(Keyword.GetLength());
            return;
        }
    }

    static TVector<CString> FlowControlKeywords=
    {
        U"if",
        U"else",
        U"for",
        U"while",
        U"return",
        U"break",
        U"continue",
    };

    for(auto& Keyword : FlowControlKeywords)
    {
        if(IsMatch(Keyword))
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.Range.EndCharIndex=CurrentCharIndex+Keyword.GetLength()-1;
            Token.TokenType=EEditorScriptTokenType::FlowControlKeyword;
            Result.TokenList.Add(Token);

            Skip(Keyword.GetLength());
            return;
        }
    }

    switch(CurrentChar)
    {
        case U' ':
        case U'\t':
        case U'\n':
        case U'\r':
        {
            return;
        }

        case U'.': 
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.Range.EndCharIndex=CurrentCharIndex;
            Token.TokenType=EEditorScriptTokenType::Dot;
            Result.TokenList.Add(Token);
            return;
        }

        case U';':
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.Range.EndCharIndex=CurrentCharIndex;
            Token.TokenType=EEditorScriptTokenType::SemiColon;
            Result.TokenList.Add(Token);
            return;
        }

        case U':':
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.Range.EndCharIndex=CurrentCharIndex;
            Token.TokenType=EEditorScriptTokenType::Colon;
            Result.TokenList.Add(Token);
            return;
        }

        case U',':
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.Range.EndCharIndex=CurrentCharIndex;
            Token.TokenType=EEditorScriptTokenType::Comma;
            Result.TokenList.Add(Token);
            return;
        }

        case U'{':
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.Range.EndCharIndex=CurrentCharIndex;
            Token.TokenType=EEditorScriptTokenType::LeftBrace;
            Result.TokenList.Add(Token);
            return;
        }

        case U'}':
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.Range.EndCharIndex=CurrentCharIndex;
            Token.TokenType=EEditorScriptTokenType::RightBrace;
            Result.TokenList.Add(Token);
            return;
        }

        case U'(':
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.Range.EndCharIndex=CurrentCharIndex;
            Token.TokenType=EEditorScriptTokenType::LeftParentheses;
            Result.TokenList.Add(Token);
            return;
        }

        case U')':
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.Range.EndCharIndex=CurrentCharIndex;
            Token.TokenType=EEditorScriptTokenType::RightParentheses;
            Result.TokenList.Add(Token);
            return;
        }

        case U'[':
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.Range.EndCharIndex=CurrentCharIndex;
            Token.TokenType=EEditorScriptTokenType::LeftBracket;
            Result.TokenList.Add(Token);
            return;
        }

        case U']':
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.Range.EndCharIndex=CurrentCharIndex;
            Token.TokenType=EEditorScriptTokenType::RightBracket;
            Result.TokenList.Add(Token);
            return;
        }

        case U'+':
        case U'-':
        case U'*':
        case U'%':
        case U'=':
        case U'!':
        case U'>':
        case U'<':
        case U'&':
        case U'|':
        case U'^':
        case U'~':
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.Range.EndCharIndex=CurrentCharIndex;
            Token.TokenType=EEditorScriptTokenType::Operator;
            Result.TokenList.Add(Token);
            return;
        }

        case U'"':
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.TokenType=EEditorScriptTokenType::LiteralString;

            while (true)
            {
                auto NextChar=GetNextChar();
                if(!NextChar.HasValue())
                {
                    //reach the end of the file
                    Token.Range.EndCharIndex=CurrentCharIndex;
                    break;
                }

                auto NextCharValue=NextChar.GetValue();
                if(NextCharValue==U'\\')
                {
                    GotoNextChar(); //skip the '\\' ,the next char might be '"' after this '\\'

                    // //skip the next char
                    // //might be \"
                    // if(!GotoNextChar()) //skip the potential '"' after '\'
                    // {
                    //     //reach the end of the file
                    //     Token.Range.EndCharIndex=CurrentCharIndex;
                    //     break;
                    // }


                }
                else if(NextCharValue==U'"')
                {
                    GotoNextChar(); //skip the '"' at the end of the string
                    Token.Range.EndCharIndex=CurrentCharIndex;
                    break;
                }

                GotoNextChar();
            }
            
            
            Result.TokenList.Add(Token);
            return;
        }

        case U'0':
        case U'1':
        case U'2':
        case U'3':
        case U'4':
        case U'5':
        case U'6':
        case U'7':
        case U'8':
        case U'9':
        {
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.TokenType=EEditorScriptTokenType::LiteralNumber;
            while(true)
            {
                auto NextChar=GetNextChar();
                if(!NextChar.HasValue())
                {
                    //reach the end of the file
                    Token.Range.EndCharIndex=CurrentCharIndex;
                    break;
                }
                auto NextCharValue=NextChar.GetValue();
                if((NextCharValue<U'0' || NextCharValue>U'9')
                    && NextCharValue!=U'.'
                )
                {
                    Token.Range.EndCharIndex=CurrentCharIndex-1;
                    break;
                }

                GotoNextChar();
            }
            Result.TokenList.Add(Token);
            return;
        }

        case U'/':
        {
            auto NextChar=GetNextChar();
            if(NextChar.HasValue())
            {
                if(NextChar.GetValue()==U'/')
                {
                    //this is  a comment
                    SEditorScriptToken Token;
                    Token.Range.StartCharIndex=CurrentCharIndex;
                    GotoNextChar(); //skip the first /

                    while(true)
                    {
                        if(!GotoNextChar())
                        {
                            //reach the end of the file
                            Token.Range.EndCharIndex=CurrentCharIndex;
                            break;
                        }
                        if(GetCurrentChar()==U'\n')
                        {
                            //reach the end of the line
                            Token.Range.EndCharIndex=CurrentCharIndex-1;
                            break;
                        }
                    }

                    Token.TokenType=EEditorScriptTokenType::Comment;
                    Result.TokenList.Add(Token);
                    return;
                }
            
                if(NextChar.GetValue()==U'*')
                {
                    //this is a multi-line comment
                    SEditorScriptToken Token;
                    Token.Range.StartCharIndex=CurrentCharIndex;
                    GotoNextChar(); //skip the first /

                    while(true)
                    {
                        if(!GotoNextChar())
                        {
                            //reach the end of the file
                            Token.Range.EndCharIndex=CurrentCharIndex;
                            break;
                        }
                        if(GetCurrentChar()==U'*')
                        {
                            auto NextChar=GetNextChar();
                            if(NextChar.HasValue() && NextChar.GetValue()==U'/' )
                            {
                                //reach the end of the comment
                                GotoNextChar(); //skip the first *
                                GotoNextChar(); //skip the second /
                                Token.Range.EndCharIndex=CurrentCharIndex;
                                break;
                            }
                        }
                    }
                    

                    Token.TokenType=EEditorScriptTokenType::Comment;
                    Result.TokenList.Add(Token);
                    return;
                }
                    
            }

            //this is / operator
            SEditorScriptToken Token;
            Token.Range.StartCharIndex=CurrentCharIndex;
            Token.Range.EndCharIndex=CurrentCharIndex;
            Token.TokenType=EEditorScriptTokenType::Operator;
            Result.TokenList.Add(Token);

            return;
        }
    }


    if(CurrentChar>=0x80
        || (CurrentChar>=U'A' && CurrentChar<=U'Z')
        || (CurrentChar>=U'a' && CurrentChar<=U'z')
        || CurrentChar==U'_'
    )
    {
        //this is a identifier
        SEditorScriptToken Token;
        Token.Range.StartCharIndex=CurrentCharIndex;
        while(true)
        {
            auto NextChar= GetNextChar();
            if(!NextChar.HasValue())
            {
                //reach the end of the file
                Token.Range.EndCharIndex=CurrentCharIndex;
                break;
            }
            auto NewCurrentChar=NextChar.GetValue();
            if(NewCurrentChar>=0x80
                || (NewCurrentChar>=U'A' && NewCurrentChar<=U'Z')
                || (NewCurrentChar>=U'a' && NewCurrentChar<=U'z')
                || NewCurrentChar==U'_'
                || (NewCurrentChar>=U'0' && NewCurrentChar<=U'9')
            )
            {
                GotoNextChar();
                continue;
            }
            else
            {
                Token.Range.EndCharIndex=CurrentCharIndex;
                break;
            }
        }

        Token.TokenType=EEditorScriptTokenType::Identifier;
        Result.TokenList.Add(Token);

        return;
    }



    if(CurrentChar<0x80)
    {
        //this is a ascii char
        SEditorScriptToken Token;
        Token.Range.StartCharIndex=CurrentCharIndex;
        Token.Range.EndCharIndex=CurrentCharIndex;
        Token.TokenType=EEditorScriptTokenType::Unknow;
        Result.TokenList.Add(Token);
        return;
    }

    //unreachable
    assert(false);
    return;
}
